/*
 * Copyright 1999-2007 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package uk.org.toot.midi.sequencer;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.File;
import java.io.FileInputStream;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import java.security.AccessController;
import java.security.PrivilegedAction;

import javax.sound.sampled.AudioPermission;

import sun.misc.Service;


/** Managing security in the Java Sound implementation.
 * This class contains all code that uses and is used by
 * SecurityManager.doPrivileged().
 *
 * @author Matthias Pfisterer
 */
class JSSecurityManager {

    /** Prevent instantiation.
     */
    private JSSecurityManager() {
    }

    /** Checks if the VM currently has a SecurityManager installed.
     * Note that this may change over time. So the result of this method
     * should not be cached.
     *
     * @return true if a SecurityManger is installed, false otherwise.
     */
    private static boolean hasSecurityManager() {
	return (System.getSecurityManager() != null);
    }


    static void checkRecordPermission() throws SecurityException {
	if(Printer.trace) Printer.trace("JSSecurityManager.checkRecordPermission()");
	SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
	    sm.checkPermission(new AudioPermission("record"));
	}
    }


    @SuppressWarnings("unchecked")
	static void loadLibrary(final String libName) {
	try {
	    if (hasSecurityManager()) {
		if(Printer.debug) Printer.debug("using security manager to load library");
		PrivilegedAction action = new PrivilegedAction() {
			public Object run() {
			    System.loadLibrary(libName);
			    return null;
			}
		    };
		AccessController.doPrivileged(action);
	    } else {
		if(Printer.debug) Printer.debug("not using security manager to load library");
		System.loadLibrary(libName);
	    }
	    if (Printer.debug) Printer.debug("loaded library " + libName);
	} catch (UnsatisfiedLinkError e2) {
	    if (Printer.err)Printer.err("UnsatisfiedLinkError loading native library " + libName);
	    throw(e2);
	}
    }


    @SuppressWarnings("unchecked")
	static String getProperty(final String propertyName) {
	String propertyValue;
	if (hasSecurityManager()) {
	    if(Printer.debug) Printer.debug("using JDK 1.2 security to get property");
	    try{
		PrivilegedAction action = new PrivilegedAction() {
			public Object run() {
			    try {
				return System.getProperty(propertyName);
			    } catch (Throwable t) {
				return null;
			    }
			}
		    };
		propertyValue = (String) AccessController.doPrivileged(action);
	    } catch( Exception e ) {
		if(Printer.debug) Printer.debug("not using JDK 1.2 security to get properties");
		propertyValue = System.getProperty(propertyName);
	    }
	} else {
	    if(Printer.debug) Printer.debug("not using JDK 1.2 security to get properties");
	    propertyValue = System.getProperty(propertyName);
	}
	return propertyValue;
    }


    /** Load properties from a file.
	This method tries to load properties from the filename give into
	the passed properties object.
	If the file cannot be found or something else goes wrong,
	the method silently fails.
	@param properties The properties bundle to store the values of the
	properties file.
	@param filename The filename of the properties file to load. This
	filename is interpreted as relative to the subdirectory "lib" in
	the JRE directory.
     */
    @SuppressWarnings("unchecked")
	static void loadProperties(final Properties properties,
			       final String filename) {
	if(hasSecurityManager()) {
	    try {
		// invoke the privileged action using 1.2 security
		PrivilegedAction action = new PrivilegedAction() {
			public Object run() {
			    loadPropertiesImpl(properties, filename);
			    return null;
			}
		    };
		AccessController.doPrivileged(action);
		if(Printer.debug)Printer.debug("Loaded properties with JDK 1.2 security");
	    } catch (Exception e) {
		if(Printer.debug)Printer.debug("Exception loading properties with JDK 1.2 security");
		// try without using JDK 1.2 security
		loadPropertiesImpl(properties, filename);
	    }
	} else {
	    // not JDK 1.2 security, assume we already have permission
	    loadPropertiesImpl(properties, filename);
	}
    }


    private static void loadPropertiesImpl(Properties properties,
					   String filename) {
	if(Printer.trace)Printer.trace(">> JSSecurityManager: loadPropertiesImpl()");
	String fname = System.getProperty("java.home");
	try {
	    if (fname == null) {
		throw new Error("Can't find java.home ??");
	    }
	    File f = new File(fname, "lib");
	    f = new File(f, filename);
	    fname = f.getCanonicalPath();
	    InputStream in = new FileInputStream(fname);
	    BufferedInputStream bin = new BufferedInputStream(in);
	    try {
		properties.load(bin);
	    } finally {
		if (in != null) {
		    in.close();
		}
	    }
	} catch (Throwable t) {
	    if (Printer.trace) {
		System.err.println("Could not load properties file \"" + fname + "\"");
		t.printStackTrace();
	    }
	}
	if(Printer.trace)Printer.trace("<< JSSecurityManager: loadPropertiesImpl() completed");
    }


/*    private static ThreadGroup getTopmostThreadGroup() {
	ThreadGroup topmostThreadGroup;
	if(hasSecurityManager()) {
	    try {
		// invoke the privileged action using 1.2 security
		PrivilegedAction action = new PrivilegedAction() {
			public Object run() {
			    try {
				return getTopmostThreadGroupImpl();
			    } catch (Throwable t) {
				return null;
			    }
			}
		    };
		topmostThreadGroup = (ThreadGroup) AccessController.doPrivileged(action);
		if(Printer.debug)Printer.debug("Got topmost thread group with JDK 1.2 security");
	    } catch (Exception e) {
		if(Printer.debug)Printer.debug("Exception getting topmost thread group with JDK 1.2 security");
		// try without using JDK 1.2 security
		topmostThreadGroup = getTopmostThreadGroupImpl();
	    }
	} else {
	    // not JDK 1.2 security, assume we already have permission
	    topmostThreadGroup = getTopmostThreadGroupImpl();
	}
	return topmostThreadGroup;
    } */


    private static ThreadGroup getTopmostThreadGroupImpl() {
	if(Printer.trace)Printer.trace(">> JSSecurityManager: getTopmostThreadGroupImpl()");
	ThreadGroup g = Thread.currentThread().getThreadGroup();
	while ((g.getParent() != null) && (g.getParent().getParent() != null)) {
	    g = g.getParent();
	}
	if(Printer.trace)Printer.trace("<< JSSecurityManager: getTopmostThreadGroupImpl() completed");
	return g;
    }


    /** Create a Thread in the topmost ThreadGroup.
     */
    @SuppressWarnings("unchecked")
	static Thread createThread(final Runnable runnable,
			       final String threadName,
			       final boolean isDaemon, final int priority,
			       final boolean doStart) {
	Thread thread = null;
	if(hasSecurityManager()) {
	    PrivilegedAction action = new PrivilegedAction() {
		    public Object run() {
			try {
			    return createThreadImpl(runnable, threadName,
						    isDaemon, priority,
						    doStart);
			} catch (Throwable t) {
			    return null;
			}
		    }
		};
	    thread = (Thread) AccessController.doPrivileged(action);
	    if(Printer.debug) Printer.debug("created thread with JDK 1.2 security");
	} else {
	    if(Printer.debug)Printer.debug("not using JDK 1.2 security");
	    thread = createThreadImpl(runnable, threadName, isDaemon, priority,
				      doStart);
	}
	return thread;
    }


    private static Thread createThreadImpl(Runnable runnable,
					   String threadName,
					   boolean isDaemon, int priority,
					   boolean doStart) {
	ThreadGroup threadGroup = getTopmostThreadGroupImpl();
	Thread thread = new Thread(threadGroup, runnable);
	if (threadName != null) {
	    thread.setName(threadName);
	}
	thread.setDaemon(isDaemon);
	if (priority >= 0) {
	    thread.setPriority(priority);
	}
	if (doStart) {
	    thread.start();
	}
	return thread;
    }


    @SuppressWarnings("unchecked")
	static List getProviders(final Class providerClass) {
	PrivilegedAction action = new PrivilegedAction() {
		@SuppressWarnings("unchecked")
		public Object run() {
		    List p = new ArrayList();
		    Iterator ps = Service.providers(providerClass);
		    while (ps.hasNext()) {
			try {
			    Object provider = ps.next();
			    if (providerClass.isInstance(provider)) {
				// $$mp 2003-08-22
				// Always adding at the beginning reverses the
				// order of the providers. So we no longer have
				// to do this in AudioSystem and MidiSystem.
				p.add(0, provider);
			    }
			} catch (Throwable t) {
			    //$$fb 2002-11-07: do not fail on SPI not found
			    if (Printer.err) t.printStackTrace();
			}								   }
		    return p;
		}
	    };
	List providers = (List) AccessController.doPrivileged(action);
	return providers;
    }
}
